// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <pollux/common/caching/ssd_file_tracker.h>
#include <algorithm>

namespace kumo::pollux::cache {
    void SsdFileTracker::fileTouched(int32_t totalEntries) {
        ++numTouches_;
        if (numTouches_ > kDecayInterval && numTouches_ > totalEntries / 2) {
            numTouches_ = 0;
            for (auto &score: regionScores_) {
                score = (score * 15) / 16;
            }
        }
    }

    void SsdFileTracker::regionFilled(int32_t region) {
        const double best =
                *std::max_element(regionScores_.begin(), regionScores_.end());
        regionScores_[region] = std::max<double>(regionScores_[region], best * 1.1);
    }

    std::vector<int32_t> SsdFileTracker::findEvictionCandidates(
        int32_t numCandidates,
        int32_t numRegions,
        const std::vector<int32_t> &regionPins) {
        // Calculates average score of regions with no pins. Returns up to
        // 'numCandidates' unpinned regions with score <= average, lowest scoring
        // region first.
        double scoreSum = 0;
        int32_t numUnpinned = 0;
        for (int i = 0; i < numRegions; ++i) {
            if (regionPins[i] > 0) {
                continue;
            }
            ++numUnpinned;
            scoreSum += regionScores_[i];
        }
        if (numUnpinned == 0) {
            return {};
        }
        const auto avg = scoreSum / numUnpinned;
        std::vector<int32_t> candidates;
        for (auto i = 0; i < regionScores_.size(); ++i) {
            if ((regionPins[i] == 0) && (regionScores_[i] <= avg)) {
                candidates.push_back(i);
            }
        }
        // Sort by score to evict less read regions first.
        std::sort(
            candidates.begin(), candidates.end(), [&](int32_t left, int32_t right) {
                return regionScores_[left] < regionScores_[right];
            });
        candidates.resize(std::min<int32_t>(candidates.size(), numCandidates));
        return candidates;
    }
} // namespace kumo::pollux::cache
